package edu.mcw.rgd.clinminer.web.userrole;

import com.vaadin.ui.AbsoluteLayout;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Field;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.PopupView;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.TabSheet.SelectedTabChangeEvent;
import com.vaadin.ui.TabSheet.SelectedTabChangeListener;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.Reindeer;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Interval;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;

import com.github.wolfie.refresher.Refresher;
import com.invient.vaadin.charts.InvientCharts;
import com.invient.vaadin.charts.InvientCharts.SeriesType;
import com.invient.vaadin.charts.InvientChartsConfig;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.spring.roo.addon.annotations.RooVaadinVisuallyComposableEntityForm;
import com.vaadin.terminal.ThemeResource;

import edu.mcw.rgd.clinminer.domain.GeneticLab;
import edu.mcw.rgd.clinminer.domain.ImagingPhenotype;
import edu.mcw.rgd.clinminer.domain.Measurement;
import edu.mcw.rgd.clinminer.domain.Medication;
import edu.mcw.rgd.clinminer.domain.NormalPhenotype;
import edu.mcw.rgd.clinminer.domain.Participant;
import edu.mcw.rgd.clinminer.domain.Phenotype;
import edu.mcw.rgd.clinminer.domain.Procedure;
import edu.mcw.rgd.clinminer.domain.common.Recordable;
import edu.mcw.rgd.clinminer.web.ui.ParticipantForm;
import edu.mcw.rgd.clinminer.web.utils.AsyncExecutor;
import edu.mcw.rgd.clinminer.web.utils.MeasurementConverter;
import edu.mcw.rgd.clinminer.web.utils.NotificationErrorTop;

/**
 * Form for editing an entity. The layout of this form can be edited with the
 * Vaadin Visual Editor.
 *
 * Fields are automatically bound to container data sources and item properties
 * based on their names (propertyId + "Field") in the aspect. Implementing
 * methods with the same name as used in the aspect allows "overriding"
 * functionality as such methods replace those from the aspect.
 */
@RooVaadinVisuallyComposableEntityForm(formBackingObject = edu.mcw.rgd.clinminer.domain.Participant.class)
@Configurable
public class ParticipantReportForm extends ParticipantForm implements
		SelectedTabChangeListener {
	private static final Logger log = Logger
			.getLogger(ParticipantReportForm.class);
	private static final long serialVersionUID = 1L;

	private Label lName;

	// data item being edited
	private Item item;

	private CheckBox filteringCheckbox;

	// used to selectively hide empty headings
	private Map<Label, Set<Layout>> headings;

	@Autowired
	AsyncExecutor asyncExecutor;

	// toggle classes for buttons
	private Class[] toggleClasses = new Class[] {
			Phenotype.class,
			ImagingPhenotype.class,
			NormalPhenotype.class,
			GeneticLab.class,
			Medication.class,
			Measurement.class,
			Procedure.class };

	private Map<Date, Integer> timelineColumnCount;
	private Map<Integer, Integer> timelineOverlap;
	private Panel flowsheetTab;
	private Panel timelineTab;
	private Panel chartsTab;
	private Layout flowsheetContent;
	private Layout timelineContent;
	private Layout chartsContent;

	public ParticipantReportForm() {
	}

	@PostConstruct
	public void init() {
		refresh();
	}

	public void updateContent(Layout layout) {
		setCompositionRoot(layout);
	}

	private Layout buildButtonLayout() {
		VerticalLayout layout = new VerticalLayout();

		filteringCheckbox = new CheckBox();
		filteringCheckbox.setValue(true);

		Layout firstRow = buildFirstRowButtonLayout();
		Layout secondRow = buildSecondRowButtonLayout();

		Panel configurationPanel = new Panel("Configuration");
		configurationPanel.addComponent(secondRow);
		configurationPanel
				.addComponent(new Label("&nbsp;", Label.CONTENT_XHTML));
		configurationPanel.addComponent(firstRow);
		configurationPanel.setSizeUndefined();

		layout.addComponent(configurationPanel);
		layout.setSpacing(false);
		layout.setMargin(true);

		return layout;
	}

	private Layout buildFirstRowButtonLayout() {
		HorizontalLayout layout = new HorizontalLayout();

		Label heading = new Label("Smart Filtering&trade;:",
				Label.CONTENT_XHTML);
		heading.setWidth(110, UNITS_PIXELS);
		layout.addComponent(heading);
		filteringCheckbox.setImmediate(true);
		ChronologicalItem.startSmartFiltering(filteringCheckbox);
		updateHeadersVisibility();
		filteringCheckbox.addListener(new ValueChangeListener() {
			@Override
			public void valueChange(ValueChangeEvent event) {
				ChronologicalItem.updateVisiblity();
				updateHeadersVisibility();
			}
		});
		layout.addComponent(filteringCheckbox);

		layout.setSpacing(true);
		layout.setMargin(false);

		return layout;
	}

	private VerticalLayout buildSpinner() {
		VerticalLayout mainLayout = new VerticalLayout();
		// common part: create layout
		mainLayout.setImmediate(false);
		mainLayout.setSizeFull();
		mainLayout.setHeight(500, UNITS_PIXELS);
		mainLayout.setMargin(false);

		Label caption = new Label("Loading...");
		caption.addStyleName(Reindeer.LABEL_H2);

		ThemeResource spinnerRes = new ThemeResource("img/loader/loader.gif");
		Embedded spinner = new Embedded(null, spinnerRes);
		spinner.setWidth(30, UNITS_PIXELS);
		spinner.setHeight(30, UNITS_PIXELS);

		Panel loadingPanel = new Panel();
		loadingPanel.setSizeUndefined();

		HorizontalLayout layout = new HorizontalLayout();
		layout.addComponent(spinner);
		layout.addComponent(new Label("&nbsp;&nbsp;", Label.CONTENT_XHTML));
		layout.addComponent(caption);
		layout.setComponentAlignment(caption, Alignment.MIDDLE_CENTER);
		layout.setComponentAlignment(spinner, Alignment.MIDDLE_CENTER);

		loadingPanel.addComponent(layout);

		mainLayout.addComponent(loadingPanel);
		mainLayout.setComponentAlignment(loadingPanel, Alignment.MIDDLE_CENTER);

		return mainLayout;
	}

	private void buildMainLayout() {
		// Configure tabsheet.
		TabSheet tabsheet = new TabSheet();
		tabsheet.setSizeFull();
		tabsheet.addStyleName(Reindeer.TABSHEET_MINIMAL);
		tabsheet.addListener(this);
		setCompositionRoot(tabsheet);

		// recreate contents
		synchronized (ParticipantReportForm.class) {
			flowsheetContent = null;
		}
		timelineContent = new AbsoluteLayout();
		chartsContent = null;

		// This will cause a selectedTabChange() call.
		// and create the base content
		flowsheetTab = new Panel();
		flowsheetTab.setSizeFull();
		tabsheet.addTab(flowsheetTab, "Flowsheet");

		// Timeline
		timelineTab = new Panel();
		timelineTab.setWidth("100%");
		timelineTab.setHeight("500px");
		timelineTab.setStyleName("c-tab-correction");
		// timelineTab.setWidth("100%");
		// timelineTab.setHeight(500, UNITS_PIXELS);
		tabsheet.addTab(timelineTab, "Timeline");

		// Charts
		chartsTab = new Panel();
		chartsTab.setSizeFull();
		tabsheet.addTab(chartsTab, "Charts");
	}

	private void configureTimeline() {
		timelineContent.setMargin(false);
		timelineContent.setStyleName("c-dummy-style");

		timelineColumnCount = new HashMap<Date, Integer>();
		timelineOverlap = new HashMap<Integer, Integer>();

		// find date range
		DateTime dob = new DateTime(item.getItemProperty("DOB").getValue());
		DateTime end = null;

		for (Recordable r : getAllAnnotations()) {
			DateTime date = new DateTime(r.getSingleDate());
			if (end == null || end.isBefore(date)) {
				end = date;
			}
		}

		// add headers
		// output
		Integer weeksAdded = 0;
		DateTime intervalEnd = dob;
		Boolean style = true;
		while (intervalEnd.isBefore(end)) {
			DateTime intervalStart = dob.plusWeeks(weeksAdded);
			intervalEnd = dob.plusWeeks(weeksAdded
					+ slidingInterval(weeksAdded));
			Interval v = new Interval(intervalStart, intervalEnd);
			Integer daysBetween = Days.daysBetween(dob, intervalStart)
					.getDays();

			Label heading = new Label(convertMonthsToCaption(weeksAdded));

			heading.addStyleName(style ? "c-timeline-header-dark"
					: "c-timeline-header-light");
			style = !style; // alternate styles betwen light and dark
			heading.setHeight(5000, UNITS_PIXELS);
			heading.addStyleName(Reindeer.LABEL_SMALL);

			String position = "left:" + (daysBetween * 10) + "px;top:" + (0)
					+ "px;";
			((AbsoluteLayout) timelineContent).addComponent(heading, position);

			weeksAdded += slidingInterval(weeksAdded);
		}
	}

	private void addToTimeline(ChronologicalItem ci) {
		// Calculate position
		Date start = (Date) item.getItemProperty("DOB").getValue();
		Date current = ci.getDate();
		Integer daysBetween = Days.daysBetween(new DateTime(start),
				new DateTime(current)).getDays();
		Integer left = daysBetween;
		Integer top = timelineColumnCount.get(current);
		if (top == null) {
			top = 0;
		}
		top++;
		// Check for overlap
		Integer furthestRight = timelineOverlap.get(top);
		Integer leftCorrected = left * 10;
		while (furthestRight != null && leftCorrected < furthestRight) {
			top++;
			furthestRight = timelineOverlap.get(top);
		}
		Integer estimatedWidth = ci.getCaption().length() * 7 + 35;
		Integer topCorrected = 15 + top * 30;
		timelineColumnCount.put(current, top);
		furthestRight = leftCorrected + estimatedWidth;
		timelineOverlap.put(top, furthestRight);
		// correct layout size
		if (timelineContent.getHeight() < topCorrected + 30) {
			timelineContent.setHeight(topCorrected + 60, UNITS_PIXELS);
		}
		if (timelineContent.getWidth() < furthestRight) {
			timelineContent.setWidth(furthestRight, UNITS_PIXELS);
		}

		// Create componenet
		String position = "left:" + leftCorrected + "px;top:" + topCorrected
				+ "px;";
		Layout eventLayout = createEventLayout(ci.getRecordable());
		Layout contentLayout = createContentLayout(ci.getRecordable());
		eventLayout.addComponent(contentLayout);
		((AbsoluteLayout) timelineContent).addComponent(eventLayout, position);
		// remove this, unnecessary
		// eventLayout.setWidth(estimatedWidth, UNITS_PIXELS);
	}

	// no need to cache as JPA is already doing it
	private List<Recordable> getAllAnnotations() {
		Property id = item.getItemProperty("id");
		Participant p = Participant.findParticipant((Long) id.getValue());
		List<Recordable> annotations = new ArrayList<Recordable>();

		annotations.addAll(p.getPhenotypes());
		annotations.addAll(p.getMedications());
		annotations.addAll(p.getImagingPhenotypes());
		annotations.addAll(p.getMeasurements());
		annotations.addAll(p.getGeneticLabs());
		annotations.addAll(p.getProcedures());
		annotations.addAll(p.getNormalPhenotypes());

		return annotations;
	}

	public Layout buildFlowsheetLayout() {
		SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
		VerticalLayout layout = new VerticalLayout();
		layout.setSizeFull();
		layout.setSpacing(false);
		layout.setMargin(true);

		// bin events by dates
		// outermap is sorted by dates (treemap) inner set is sorted by event
		// type (treeset)
		Map<DateTime, Set<Recordable>> eventDates = new TreeMap<DateTime, Set<Recordable>>();
		for (Recordable r : getAllAnnotations()) {
			DateTime date = new DateTime(r.getSingleDate());
			Set<Recordable> events = eventDates.get(date);
			if (events == null) {
				events = new TreeSet<Recordable>(new Comparator<Recordable>() {
					@Override
					public int compare(Recordable r1, Recordable r2) {
						// phenotypes come before labs in this sorting?
						int comparison = -r1.getClass().getCanonicalName()
								.compareTo(r2.getClass().getCanonicalName());
						// IMPORTANT otherwise set collapses similar keys
						if (comparison == 0) {
							return 1;
						}
						return comparison;
					}
				});
				eventDates.put(date, events);
			}
			events.add(r);
		}

		// output
		DateTime dob = new DateTime(item.getItemProperty("DOB").getValue());
		Integer weeksAdded = 0;

		while (!eventDates.isEmpty()) {
			Boolean addedHeading = false;
			DateTime start = dob.plusWeeks(weeksAdded);
			DateTime end = dob.plusWeeks(weeksAdded
					+ slidingInterval(weeksAdded));
			Interval v = new Interval(start, end);

			for (DateTime d : new ArrayList<DateTime>(eventDates.keySet())) {
				if (v.contains(d)) {
					Set<Layout> headerEvents = new HashSet<Layout>();
					if (!addedHeading) {
						Label heading = new Label(
								convertMonthsToCaption(weeksAdded));
						// heading.addStyleName("c-event-layout-padding");
						heading.addStyleName("c-event-header-highlight");
						heading.addStyleName(Reindeer.LABEL_H2);
						layout.addComponent(heading);
						addedHeading = true;
						headings.put(heading, headerEvents);
					}

					Label labelDate = new Label(df.format(d.toDate()));
					labelDate.addStyleName(Reindeer.LABEL_SMALL);
					labelDate.addStyleName("c-event-layout-padding");
					layout.addComponent(labelDate);
					Set<Layout> subHeaderEvents = new HashSet<Layout>();
					headings.put(labelDate, subHeaderEvents);

					// List events
					for (final Recordable r : eventDates.get(d)) {
						Layout eventLayout = createEventLayout(r);
						eventLayout.addStyleName("c-event-layout-padding");
						Layout contentLayout = createContentLayout(r);

						ChronologicalItem ci = new ChronologicalItem(r,
								contentLayout, eventLayout);
						addToTimeline(ci);

						// header toggles
						headerEvents.add(eventLayout);
						subHeaderEvents.add(eventLayout);

						eventLayout.addComponent(contentLayout);
						layout.addComponent(eventLayout);
					}
					// remove from subsequent generation
					eventDates.remove(d);
				}
			}

			weeksAdded += slidingInterval(weeksAdded);
		}

		return layout;
	}

	public Layout createContentLayout(Recordable r) {
		HorizontalLayout contentLayout = new HorizontalLayout();
		contentLayout.setMargin(false);
		contentLayout.setSpacing(true);

		// create popout
		Label summaryLabel = new Label(getSummaryDescription(r),
				Label.CONTENT_XHTML);
		summaryLabel.setWidth(400, UNITS_PIXELS);
		summaryLabel.setStyleName(Reindeer.LABEL_H2);
		PopupView popup = new PopupView(getCaption(r), summaryLabel);
		popup.addStyleName("c-chronologicalitem-popup");
		popup.setHideOnMouseOut(false);
		popup.addStyleName("c-event-padding");

		contentLayout.addComponent(popup);

		return contentLayout;
	}

	public Layout createEventLayout(Recordable r) {
		AbsoluteLayout eventLayout = new AbsoluteLayout();
		eventLayout.setHeight(27, UNITS_PIXELS);
		eventLayout.setMargin(false);

		ThemeResource drugResource = new ThemeResource(
				"img/medicine-pill-th.png");
		ThemeResource xrayResource = new ThemeResource("img/xray-th.png");
		ThemeResource labResource = new ThemeResource("img/blue-flask-th.png");
		ThemeResource otherResource = new ThemeResource(
				"img/darkgraycircleindicator-th.png");

		// add icons
		if (r instanceof Medication) {
			Embedded pillIcon = new Embedded("", drugResource);
			pillIcon.setWidth(20, UNITS_PIXELS);
			pillIcon.setHeight(20, UNITS_PIXELS);
			eventLayout.addComponent(pillIcon, "top:0px;left:0px;");
		} else if (r instanceof ImagingPhenotype) {
			Embedded icon = new Embedded("", xrayResource);
			icon.setWidth(20, UNITS_PIXELS);
			icon.setHeight(20, UNITS_PIXELS);
			eventLayout.addComponent(icon, "top:0px;left:0px;");
		} else if (r instanceof Measurement) {
			Embedded icon = new Embedded("", labResource);
			icon.setWidth(20, UNITS_PIXELS);
			icon.setHeight(20, UNITS_PIXELS);
			eventLayout.addComponent(icon, "top:0px;left:0px;");
		} else {
			Embedded icon = new Embedded("", otherResource);
			icon.setWidth(20, UNITS_PIXELS);
			icon.setHeight(20, UNITS_PIXELS);
			eventLayout.addComponent(icon, "top:0px;left:0px;");
		}

		return eventLayout;
	}

	private Layout buildChartsContent() {
		if (chartsContent != null) {
			return chartsContent;
		}
		// set up
		chartsContent = new VerticalLayout();
		chartsContent.setSizeFull();
		chartsContent.setMargin(true);

		InvientChartsConfig chartConfig = new InvientChartsConfig();
		chartConfig.getGeneralChartConfig().setType(SeriesType.LINE);
		chartConfig.getTitle().setText("Labs");
		// chartConfig.getSubtitle().setText("Clinminer participant name");

		chartConfig.getCredit().setEnabled(false);

		InvientCharts invChart = ChronologicalItem
				.createChartsData(chartConfig);

		chartsContent.addComponent(invChart);
		return chartsContent;
	}

	private Layout buildFlowsheetContent() {
		if (flowsheetContent != null) {
			return flowsheetContent;
		}
		synchronized (ParticipantReportForm.class) {
			flowsheetContent = new VerticalLayout();

			configureTimeline();
			headings = new HashMap<Label, Set<Layout>>();
			ChronologicalItem.reset(toggleClasses);

			// common part: create layout
			flowsheetContent.setWidth("100.0%");
			flowsheetContent.setHeight("-1px");
			flowsheetContent.setMargin(true);
			((VerticalLayout) flowsheetContent).setSpacing(true);

			// lName
			lName = new Label();
			lName.setImmediate(false);
			lName.setWidth("-1px");
			lName.setHeight("-1px");
			lName.setValue("Label");
			lName.addStyleName(Reindeer.LABEL_H1);
			if (item != null) {
				lName.setValue(item.getItemProperty("name").getValue());
			}

			flowsheetContent.addComponent(lName);

			// note need reportlayout first for counts in buttonlayout
			Layout reportLayout = buildFlowsheetLayout();
			Layout buttonLayout = buildButtonLayout();

			flowsheetContent.addComponent(buttonLayout);
			flowsheetContent.addComponent(reportLayout);

			return flowsheetContent;
		}
	}

	private Layout buildSecondRowButtonLayout() {
		HorizontalLayout layout = new HorizontalLayout();
		Label heading = new Label("Show :");
		heading.setWidth(110, UNITS_PIXELS);
		layout.addComponent(heading);

		for (final Class c : toggleClasses) {
			final CheckBox bShow = new CheckBox();
			Integer count = ChronologicalItem.getCount(c);
			if (count == 0) {
				bShow.setEnabled(false);
			}
			bShow.setCaption(c.getSimpleName() + "s (" + count + ")");

			bShow.setValue(true);
			bShow.setImmediate(true);
			if (layout.getComponentCount() > 0
					&& layout.getComponent(layout.getComponentCount() - 1) instanceof CheckBox) {
				layout.addComponent(new Label(" | "));
			}
			layout.addComponent(bShow);
			layout.setComponentAlignment(bShow, Alignment.MIDDLE_CENTER);

			bShow.addListener(new ValueChangeListener() {
				@Override
				public void valueChange(ValueChangeEvent event) {
					// toggle events
					Boolean isVisible = (Boolean) event.getProperty()
							.getValue();
					ChronologicalItem.setVisibiltyClass(c, isVisible);

					// toggle headers
					updateHeadersVisibility();
				}
			});
		}

		layout.setSpacing(true);
		layout.setMargin(false);
		return layout;
	}

	private String convertMonthsToCaption(Integer weeksAdded) {
		if (weeksAdded == 0) {
			return "Postnatal";
		}

		Integer total = weeksAdded;
		Integer years = total / 52; // 52 weeks in a year
		Integer remainingMonths = (total % 52) / 4;
		Integer remainingWeeks = (total % 52) % 4;

		String yearPrefix = "";
		String monthPrefix = "";
		String weekPrefix = "";

		if (years != 0) {
			yearPrefix = years + "-year ";
		}
		if (remainingMonths != 0) {
			monthPrefix = remainingMonths + "-month ";
		}
		if (remainingWeeks != 0) {
			weekPrefix = remainingWeeks + "-week ";
		}

		String age = yearPrefix + monthPrefix + weekPrefix;

		return age.substring(0, age.length() - 1) + "-old";
	}

	public Field getField(Object propertyId) {
		return null;
	}

	@Override
	public void refresh() {
		if (item == null) {
			return;
		}
	}

	@Override
	public void setItemDataSource(Item item) {
		// delay changes until update completes elsewhere
		this.item = item;
		// skip if no item selected
		if (item == null) {
			return;
		}

		buildMainLayout();
	}

	private Integer slidingInterval(Integer weeks) {
		if (weeks < 4) { // up to 2 months it is one week resolution
			return 1;
		} else if (weeks >= 4 && weeks < 52) { // up to 12 months it's a
												// month resolution
			return 4;
		} else if (weeks >= 52 && weeks < 52 * 4) { // up to 4 years
													// it's a quarter
			return 4 * 3;
		} else {
			return 52; // one year afterwards
		}
	}

	private void updateHeadersVisibility() {
		for (Entry<Label, Set<Layout>> e : headings.entrySet()) {
			Boolean isAnythingVisible = false;
			for (Layout l : e.getValue()) {
				if (l.isVisible()) {
					isAnythingVisible = true;
					break;
				}
			}
			if (isAnythingVisible) {
				e.getKey().setVisible(true);
			} else {
				e.getKey().setVisible(false);
			}
		}
	}

	public String getSummaryDescription(Recordable recordable) {
		String observationPart = "";
		String preposition = "";
		String highlightStart = "";
		String highlightEnd = "";
		String eventPart = "";
		String documentPart = "";
		String source = "";

		String originalMeasurement = "";
		SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy");

		if (recordable.getDirectObservation() != null
				&& recordable.getDirectObservation()) {
			observationPart = "<b>Direct</b> observation";
		} else {
			observationPart = "Reported occurance";
		}

		if (recordable.getExactDate() != null && recordable.getExactDate()) {
			preposition = " on ";
			highlightStart = "<b>";
			highlightEnd = "</b>";
		} else {
			preposition = " around ";
		}

		if (recordable.getEventDate() != null) {
			eventPart = preposition + highlightStart
					+ sdf.format(recordable.getEventDate()) + highlightEnd;
		}

		if (recordable.getDocumentSourceDate() != null) {
			documentPart = " documented on "
					+ sdf.format(recordable.getDocumentSourceDate());
		}

		if (recordable.getDocumentSource() != null) {
			source = " in "
					+ recordable.getDocumentSource().replaceAll(
							"[^A-Za-z_:0-9/]", " ");
		}

		if (recordable instanceof Measurement) {
			MeasurementConverter converter = new MeasurementConverter(
					recordable);
			if (converter.getUnit() != null
					&& !converter.getOriginalUnit().equals(converter.getUnit())) {
				originalMeasurement = "<br><br/>original value: <b>"
						+ converter.getOriginalValue() + " "
						+ converter.getOriginalUnit();
			}
		}

		return observationPart + eventPart + documentPart + source + " as <i>"
				+ recordable.getAnnotatedText() + "</i>" + originalMeasurement;
	}

	public String getCaption(Recordable recordable) {
		String coreLabel = getCoreLabel(recordable);

		String additionalText = "";
		if (recordable instanceof Measurement) {

			if (((Measurement) recordable).getValue() != null) {
				additionalText = " " + ((Measurement) recordable).getValue();
				if (((Measurement) recordable).getUnit() != null) {
					additionalText += " "
							+ ((Measurement) recordable).getUnit();
				}
			}
		} else if (recordable instanceof Medication) {

			if (((Medication) recordable).getDailyDosage() != null) {
				additionalText = " "
						+ ((Medication) recordable).getDailyDosage();
			}
			if (((Medication) recordable).getDoseForm() != null) {
				additionalText = " [" + ((Medication) recordable).getDoseForm()
						+ "]";
			}
		}
		return coreLabel + additionalText;

	}

	public String getCoreLabel(Recordable recordable) {
		String coreLabel = null;
		if (recordable instanceof Measurement) {
			coreLabel = ((Measurement) recordable).getLOINCLabel();
		} else if (recordable instanceof GeneticLab) {
			coreLabel = ((GeneticLab) recordable).getTestedPhenotypeLabel();
		} else if (recordable instanceof Medication) {
			coreLabel = recordable.getOntologyTermLabel();
		} else {
			coreLabel = recordable.getOntologyTermLabel();
		}

		if (coreLabel == null) {
			coreLabel = recordable.getAnnotatedText();
		}
		return coreLabel;
	}

	@Override
	public void selectedTabChange(SelectedTabChangeEvent event) {
		TabSheet tabsheet = (TabSheet) event.getSource();
		// set up loading message

		final Panel selectedTab = (Panel) tabsheet.getSelectedTab();
		final VerticalLayout spinner = buildSpinner();
		final Refresher refresher = new Refresher();
		refresher.setRefreshInterval(100);
		spinner.addComponent(refresher);
		selectedTab.addComponent(spinner);

		if (selectedTab == flowsheetTab) {
			asyncExecutor.run(new Runnable() {
				@Override
				public void run() {
					try {
						buildFlowsheetContent();
						updateContent(selectedTab, flowsheetContent, spinner);
					} catch (Exception e) {
						getApplication().getMainWindow().showNotification(
								new NotificationErrorTop("Report Error", e
										.toString()));
						e.printStackTrace();
					}
				}
			});
		} else if (selectedTab == timelineTab) {
			asyncExecutor.run(new Runnable() {
				@Override
				public void run() {
					try {
						updateContent(selectedTab, timelineContent, spinner);
					} catch (Exception e) {
						getApplication().getMainWindow().showNotification(
								new NotificationErrorTop("Report Error", e
										.toString()));
						e.printStackTrace();
					}
				}
			});
		} else if (selectedTab == chartsTab) {
			asyncExecutor.run(new Runnable() {
				@Override
				public void run() {
					try {
						buildChartsContent();
						updateContent(selectedTab, chartsContent, spinner);
					} catch (Exception e) {
						getApplication().getMainWindow().showNotification(
								new NotificationErrorTop("Report Error", e
										.toString()));
						e.printStackTrace();
					}
				}
			});
		}
	}

	private void updateContent(Panel panel, Layout content, Layout spinner) {
		// make sure spineer has time to show up
		try {
			Thread.sleep(100);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		panel.setContent(content);
		panel.removeComponent(spinner);
	}

}
